package com.open.lock.component;

import com.open.lock.config.RedissonProperties;
import com.open.lock.enums.LockModel;
import com.open.lock.exception.LockException;
import com.open.lock.utils.LockUtil;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 分布式锁
 *
 * @author zhang kaichao
 */
public class CloudLock {

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedissonProperties properties;

    /**
     * 加锁，一直等待获取锁，无自动释放锁时间
     *
     * @param lockModel 锁的模式
     * @param keys      键，根据模式生成list或string
     * @return 锁
     */
    public RLock tryLock(LockModel lockModel, String... keys) {
        return tryLock(lockModel, null, null, keys);
    }

    /**
     * 加锁，设置等待加锁超时时间，无自动释放锁时间
     *
     * @param lockModel      锁的模式
     * @param attemptTimeout 等待加锁超时时间
     * @param keys           键，根据模式生成list或string
     * @return 锁
     */
    public RLock tryLock(LockModel lockModel, Long attemptTimeout, String... keys) {
        return tryLock(lockModel, attemptTimeout, null, keys);
    }

    /**
     * 加锁，设置等待加锁超时时间，设置自动释放锁时间
     *
     * @param lockModel      锁的模式
     * @param attemptTimeout 等待加锁超时时间
     * @param leaseTime      自动释放锁的时间
     * @param keys           键，根据模式生成list或string
     * @return 锁
     */
    public RLock tryLock(LockModel lockModel, Long attemptTimeout, Long leaseTime, String... keys) {
        RLock lock = getLock(lockModel, keys);
        boolean res;
        if (leaseTime == null) {
            leaseTime = -1L;
        }
        if (attemptTimeout == null || attemptTimeout <= -1) {
            lock.lock(leaseTime, TimeUnit.MILLISECONDS);
            res = true;
        } else {
            try {
                res = lock.tryLock(attemptTimeout, leaseTime, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
                throw new LockException("Lock ==> tryLock is error.");
            }
        }
        if (!res) {
            throw new LockException("Lock ==> tryLock result is false.");
        }
        return lock;
    }

    /**
     * 释放锁
     */
    public void unlock(RLock rLock) {
        if (rLock.isHeldByCurrentThread()) {
            rLock.unlock();
        }
    }

    public RLock getIdempotentLock(LockModel lockModel, String... keys){
        List<String> keyList = LockUtil.keysToList(keys);
        String key = String.format("%s:%s", properties.getPrefix(), String.join(":", keyList));
        List<String> prefixList = keyList.stream().map(m -> String.format("%s:%s", properties.getPrefix(), m)).collect(Collectors.toList());
        return buildLock(lockModel,key,prefixList);
    }

    /**
     * 获取锁
     */
    public RLock getLock(LockModel lockModel, String... keys) {
        List<String> keyList = LockUtil.keysToList(keys);
        String key = String.format("%s:%s", properties.getPrefix(), LockUtil.lockKey(String.join(":", keyList)));
        List<String> prefixList = keyList.stream().map(m -> String.format("%s:%s", properties.getPrefix(), LockUtil.lockKey(m))).collect(Collectors.toList());
        return buildLock(lockModel,key,prefixList);
    }

    private RLock buildLock(LockModel lockModel,String key,List<String> prefixList){
        RLock rLock;
        switch (lockModel) {
            case FAIR:
                rLock = fairLock(key);
                break;
            case REDLOCK:
                rLock = redLock(prefixList);
                break;
            case MULTIPLE:
                rLock = multiLock(prefixList);
                break;
            case REENTRANT:
                rLock = reentrantLock(key);
                break;
            case READ:
                rLock = readLock(key);
                break;
            case WRITE:
                rLock = writeLock(key);
                break;
            default:
                rLock = readLock(key);
                break;
        }
        if (rLock == null) {
            throw new LockException("Lock ==> get lock fail.");
        }
        return rLock;
    }

    /**
     * 公平锁
     */
    private RLock fairLock(String key) {
        return redissonClient.getFairLock(key);
    }

    /**
     * 红锁
     */
    private RLock redLock(List<String> keys) {
        RLock[] locks = keys.stream().filter(f -> f != null && !"".equals(f)).map(x -> redissonClient.getLock(x)).toArray(RLock[]::new);
        return new RedissonRedLock(locks);
    }

    /**
     * 联锁
     */
    private RLock multiLock(List<String> keys) {
        RLock[] locks = keys.stream().filter(f -> f != null && !"".equals(f)).map(x -> redissonClient.getLock(x)).toArray(RLock[]::new);
        return new RedissonMultiLock(locks);
    }

    /**
     * 可重入锁
     */
    private RLock reentrantLock(String key) {
        return redissonClient.getLock(key);
    }

    /**
     * 读锁
     */
    private RLock readLock(String key) {
        return redissonClient.getReadWriteLock(key).readLock();
    }

    /**
     * 写锁
     */
    private RLock writeLock(String key) {
        return redissonClient.getReadWriteLock(key).writeLock();
    }

}
